//
// Written by Motti Shaked
//
// motti@wincode.net
//
// Version: 1.0.0.0
//
// DISCLAIMER:
//
// You may use this code as you please.
// You may modify and share it freely.
// You may NOT hold me liable for any damage
// caused to you, your company, your neighbors,
// or anyone else as a result of using this code,
// taking ideas from it, dreaming of it,
// or any other use you may find it suitable for.
// Whatever you do with this code is at 
// your own risk.
//
// Enjoy!
// 

using System;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using System.Collections;
using System.Reflection;
using Universe.Remoting.Sinks;

namespace Universe.Remoting.Sinks
{
    public class CustomServerSinkProvider : BaseCustomSinkProvider, IServerChannelSinkProvider
    {
        private IServerChannelSinkProvider nextProvider;

        public CustomServerSinkProvider(IDictionary properties, ICollection providerData)
            : base(properties, providerData)
        {
        }


        public CustomServerSinkProvider(Type customServerSink)
            : this(CreateProperties(customServerSink), new ArrayList())
        {
        }


        public IServerChannelSinkProvider Next
        {
            get {return this.nextProvider; }
            set {this.nextProvider = value;}
        }

        public IServerChannelSink CreateSink(IChannelReceiver channel) 
        {
            BaseCustomSink customSinkObject = null;
            bool keepCreatingInstance = false;

            // the custom sink must be provided the perProviderState object.
            // passing this object after construction is too late, because the sink
            // may need it in its constructor.
            // however, I did not want to affect the signature of every
            // possible sink constructor (and wanted to
            // allow writing basic custom sinks without explicitly writing a constructor).
            // so I pass it through the CallContext.
            CallContext.SetData("perProviderState", this.perProviderState);

            // try to instantiate the custom sink
            // with a constructor that takes ServerSinkCreationData (or SinkCreationData)
            object [] par = {new ServerSinkCreationData(this.data, channel)};
            try
            {
                customSinkObject = (BaseCustomSink)Activator.CreateInstance(this.customSinkType, par);
            }
            catch (MissingMethodException)
            {
                // do nothing - try to create using a parameterless constructor
                keepCreatingInstance = true;
            }
            catch (System.Exception e)
            {
                ExamineConstructionException(e);
            }

            // if there is no constructor that takes ServerSinkCreationData or SinkCreationData,
            // look for a parameterless constructor
            if (keepCreatingInstance)
            {
                try
                {
                    customSinkObject = (BaseCustomSink)Activator.CreateInstance(customSinkType);
                }
                catch (System.Exception e)
                {
                    ExamineConstructionException(e);
                }
            }

            // create next sink in the chain
            IServerChannelSink next = nextProvider.CreateSink(channel);

            if (customSinkObject != null)
            {
                customSinkObject.SetNextSink(next);
                return customSinkObject;
            }
            else
            {
                return next;
            }
        }

        public void GetChannelData(IChannelDataStore channelData)
        {
        }

        // this method has to examine the exception thrown from
        // a derived class' constructor and act accordingly
        private void ExamineConstructionException(System.Exception e)
        {
            if (!(e.InnerException is ExcludeMeException))
            {
                if (e is TargetInvocationException)
                {
                    throw new CustomSinkException(
                        string.Format("Could not create instance of {0}. {1} was thrown during construction. Message: {2}", 
                                      this.customSinkType.ToString(), e.InnerException.GetType().ToString(),
                                      e.InnerException.Message), e);
                }
                else
                {
                    throw new CustomSinkException(
                        string.Format("Could not create instance of {0}.", 
                                      this.customSinkType.ToString()), e);
                }
            }
        }
    }
}